package com.dana.friend.service.user.impl;

import cn.hutool.core.util.RandomUtil;
import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.core.constants.CacheConstant;
import com.core.constants.Constants;
import com.core.constants.HttpConstants;
import com.core.domain.LoginUser;
import com.core.domain.Result;
import com.core.domain.vo.LoginUserVO;
import com.core.enums.ResultCode;
import com.core.enums.UserIdentity;
import com.core.utils.ThreadLocalUtils;
import com.dana.friend.domain.user.User;
import com.dana.friend.domain.user.dto.EditUserDTO;
import com.dana.friend.domain.user.dto.UserDTO;
import com.dana.friend.domain.user.vo.UserVO;
import com.dana.friend.manager.UserCacheManager;
import com.dana.friend.mapper.user.UserMapper;
import com.dana.friend.service.user.IUserService;
import com.dana.redis.service.RedisService;
import com.dana.security.exception.ServiceException;
import com.dana.security.service.TokenService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;

import java.time.LocalDateTime;
import java.time.temporal.ChronoUnit;
import java.util.concurrent.TimeUnit;

@Slf4j
@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User>
        implements IUserService {
    @Autowired
    private RedisService redisService;
    @Autowired
    private TokenService tokenService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private UserCacheManager userCacheManager;

    //验证码有效期
    @Value("${sms.code-expiration:5}")
    private Long phoneCodeExpiration;


    //发送验证码的每天的限制次数
    @Value("${sms.send-limit:50}")
    private Integer sendLimit;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${file.oss.downloadUrl}")
    private String downloadUrl;

    @Override
    public void sendCode(UserDTO userSendCodeDTO) {
        //校验手机号格式
        boolean validPhoneNumber = isValidPhoneNumber(userSendCodeDTO.getPhone());
        if (!validPhoneNumber) {
            throw new ServiceException(ResultCode.FAILED_USER_PHONE);
        }
        //phoneCodeKey是缓存中redis存储的验证码前缀
        String phoneCodeKey = getPhoneCodeKey(userSendCodeDTO.getPhone());
        //每隔一分钟重新发送一次请求,不能请求太频繁.
        //拿到剩余时间,如果和初始时间相差不到一分钟抛出操作频繁的异常信息
        Long expire = redisService.getExpire(phoneCodeKey, TimeUnit.SECONDS);
        if (expire != null && (phoneCodeExpiration * CacheConstant.ONE_MINUTES - expire) < CacheConstant.ONE_MINUTES) {
            throw new ServiceException(ResultCode.FREQUENT_OPERATIONS);
        }

        //对每天获取验证码的次数做限制,如果当天达到限制次数,第二天清零重新计算
        //获取当前发送验证码次数
        String codeTimeKey = getCodeTimeKey(userSendCodeDTO.getPhone());
        Long sendTimes = redisService.getCacheObject(codeTimeKey, Long.class);
        //如果当前次数不等于null并且请求次数超过了规定的次数
        if (sendTimes != null && sendTimes >= sendLimit) {
            throw new ServiceException(ResultCode.TIMES_UPPER_LIMIT);
        }
        //使用hutools生成随机验证码
        String code = RandomUtil.randomNumbers(6);
        //将验证码存储到redis中用于校验
        redisService.setCacheObject(phoneCodeKey, code, phoneCodeExpiration, TimeUnit.MINUTES);
        //代替阿里云验证码发送
        log.info("生成的验证码为:{}", code);
        redisService.increment(codeTimeKey);
        if (sendTimes == null) {//证明今天是第一次登录
            long seconds = ChronoUnit.SECONDS.between(LocalDateTime.now(), LocalDateTime.now().plusDays(1)
                    .withHour(0).withMinute(0).withSecond(0).withNano(0));
            redisService.expire(codeTimeKey, seconds, TimeUnit.SECONDS);
        }
    }

    /**
     * 管理员退出登录
     * 并删除redis中的tokenKey
     * @param token
     * @return
     */
    @Override
    public boolean logout(String token) {
        String tokens = deleteBearer(token);
        //在缓存中删除token
        return tokenService.deleteLoginUser(tokens,secret);
    }

    /**
     * 获取当前登录用户的信息
     * @param token
     */
    @Override
    public Result<LoginUserVO> info(String token) {
        //将Bearer前缀去掉
        String tokens = deleteBearer(token);
        //从缓存中获取loginUser
        LoginUser loginUser = tokenService.getLoginUser(tokens, secret);
        if (loginUser==null){
            return Result.fail();
        }
        LoginUserVO loginUserVO=new LoginUserVO();
        loginUserVO.setNickName(loginUser.getNickName());
        //更换头像
        if (StrUtil.isNotEmpty(loginUser.getHeadImage())){
            loginUserVO.setHeadImage(downloadUrl+loginUser.getHeadImage());
        }
        return Result.ok(loginUserVO);
    }

    /**
     * 获取用户详情信息
     * 从缓存获取,缓存没有去数据库查并且重新同步到缓存
     * @return
     */
    @Override
    public UserVO detail() {
        //通过ThreadLocal获取userId
        Long userId = ThreadLocalUtils.get(Constants.USER_ID,Long.class);
        if (userId==null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        //获取userVO,缓存中不存在就去数据库查询并且更新缓存
        UserVO userVO = userCacheManager.getUserById(userId);
        if (userVO==null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        //设置头像
        if (StrUtil.isNotEmpty(userVO.getHeadImage())){
            userVO.setHeadImage(downloadUrl+userVO.getHeadImage());
        }
        return userVO;
    }

    /**
     * 用户基本信息修改
     * @param editUserDTO
     */
    @Override
    public int edit(EditUserDTO editUserDTO) {
        Long userId = ThreadLocalUtils.get(Constants.USER_ID, Long.class);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserId, userId));
        if (user==null){
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setPhone(editUserDTO.getPhone());
        user.setNickName(editUserDTO.getNickName());
        user.setSex(editUserDTO.getSex());
        user.setEmail(editUserDTO.getEmail());
        user.setWechat(editUserDTO.getWechat());
        user.setSchoolName(editUserDTO.getSchoolName());
        user.setMajorName(editUserDTO.getMajorName());
        user.setIntroduce(editUserDTO.getIntroduce());
        //修改用户基本信息的缓存
        userCacheManager.refreshUser(user);
        //修改当前用户昵称,头像的缓存
        tokenService.refreshLoginUser(user.getNickName(),user.getHeadImage(),
                ThreadLocalUtils.get(Constants.USER_KEY,String.class));
        return userMapper.updateById(user);
    }

    /**
     * 修改用户头像
     * 前端点击上传头像之后,调用upload接口,将头像上传到阿里云的oss中然后会生成一个文件名返回给前端
     * 紧接着调用updateImage这个方法,前端带上这个文件名之后传递给后端,后端进行保存到数据库和redis中
     * @param headImage
     * @return
     */
    @Override
    public int updateImage(String headImage) {
        Long userId = ThreadLocalUtils.get(Constants.USER_ID, Long.class);
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getUserId, userId));
        if (user == null) {
            throw new ServiceException(ResultCode.FAILED_USER_NOT_EXISTS);
        }
        user.setHeadImage(headImage);
        //修改用户基本信息的缓存
        userCacheManager.refreshUser(user);
        //修改当前用户昵称,头像的缓存
        tokenService.refreshLoginUser(user.getNickName(), user.getHeadImage(),
                ThreadLocalUtils.get(Constants.USER_KEY, String.class));
        return userMapper.updateById(user);
    }

    @Override
    public String codeLogin(String phone, String code) {
        //不管新老都先校验验证码,验证码正确再判断是新老用户,新用户存入数据库,老用户直接生成token
        checkCode(phone, code);
        //判断是新用户还是老用户
        User user = userMapper.selectOne(new LambdaQueryWrapper<User>().eq(User::getPhone, phone));
        if(user==null){
            //注册
            user=new User();
            user.setPhone(phone);
            userMapper.insert(user);
        }
        //生成jwt
        return tokenService.
                createToken(user.getUserId(), secret, UserIdentity.ORDINARY.getValue(),
                        user.getNickName(),user.getHeadImage());
    }



    /**
     * 删除将Bearer前缀
     * @param token
     * @return
     */
    private String deleteBearer(String token){
        if (StrUtil.isNotEmpty(token) && token.startsWith(HttpConstants.PREFIX)) {
            token = token.replaceFirst(HttpConstants.PREFIX, StrUtil.EMPTY);
        }
        return token;
    }

    private void checkCode(String phone, String code) {
        String phoneCodeKey = getPhoneCodeKey(phone);
        String cacheCode = redisService.getCacheObject(phoneCodeKey, String.class);
        if (StrUtil.isEmpty(cacheCode)){
            throw new ServiceException(ResultCode.CODE_PAST_DUE);
        }
        if (!cacheCode.equals(code)){
            throw new ServiceException(ResultCode.CODE_IS_ERROR);
        }
        
        redisService.deleteObject(phoneCodeKey);
    }


    private  boolean isValidPhoneNumber(String phoneNumber) {
        String regex = "^1[3-9]\\d{9}$";
        return phoneNumber.matches(regex);
    }

    private String getPhoneCodeKey(String phone){
        return CacheConstant.PHONE_CODE_KEY+phone;
    }

    private String getCodeTimeKey(String phone){
        return CacheConstant.CODE_TIME_KEY+phone;
    }

}
